/******************************************************************************/
/* zzg.ccp : zzg Version 1.3                                                  */
/* A C++/Qt4 program : to plan scheduled computer shut down                   */
/* Ptyxs (Cassian Braconnier) - july 2010                                     */
/* Lic. GPLv3                                                                 */
/* Provided with no guaranty whatsoever.                                      */
/******************************************************************************/

//------------------------------------------------------------------------------

#include <QtGui>


#include "zzg.h"
#include "whichdesktop.h"
#include "mainwindow.h"

//------------------------------------------------------------------------------

#define QTSOUNDRSCBUG

//------------------------------------------------------------------------------

// valeur par défaut de la durée restante avant extinction
const uint Zzg::defaultRemnantTime = 120;

// durée d'attente maximum (en minutes)
const uint Zzg::maxRemnantTime = 99999;
// nombre de chiffres du nombre maxRemnantTime
const uint Zzg::maxRemnantLength = QString::number(maxRemnantTime).size();

// intervalle (en millisecondes) entre deux 'clics' du chrono
const uint Zzg::timerInterval = 1000; // toutes les secondes

// les commandes pour éteindre l'ordinateur selon bureau
//pour GNOME
const QString Zzg::gnomeCmd = "/usr/bin/gnome-session-save";
const QString Zzg::gnomeArgs = "--shutdown-dialog";
//pour KDE
const QString Zzg::kdeCmd = Zzg::findQdbus();
const QString Zzg::kdeArgs = "org.kde.ksmserver /KSMServer org.kde.KSMServerInterface.logout 0 2 2";
//autre bureau
// suppose l'utilisateur sudoer pour shutdown
const QString Zzg::otherCmd = "/usr/bin/halt";
const QString Zzg::otherArgs = "";

// liste des bureaux reconnus par Zzg
const QMap<QString, QString> mapProcessToDesktop;

// identité du bureau courant (GNOME, KDE...)
const QString Zzg::desktop = whichDesktop();

// localisation des fichiers de son
const QString Zzg::soundResourceDir = QString(":/sounds/");

#ifdef QTSOUNDRSCBUG
const QString Zzg::soundCopyDir =  QDir::toNativeSeparators (QDir::tempPath() + "/");
const QString Zzg::soundToPlayDir = soundCopyDir;
#else
const QString Zzg::soundToPlayDir = soundResourceDir;
#endif

// liste des fichiers sons à exécuter
const QList<QUrl> Zzg::queue = setNotificationSoundQueue();

// durée d'affichage du dialogue lancé par GNOME
const uint Zzg::gnomeDialogDisplayTime = 60;

//------------------------------------------------------------------------------

// le constructeur
Zzg::Zzg(QWidget *parent) : QDialog(parent)
{

// le validateur (permet seulement 0 à 99999 minutes = 69,4 jours)

     QString rx = "^[0-9]{1," + QString::number(maxRemnantLength) + "}$";
     QRegExp regexp(rx);
     myValidator = new QRegExpValidator(regexp, this);

// première couche horizontale de la fenêtre

     label1 = new QLabel(tr("A&rrêt souhaité de l'ordinateur dans :"));
     lineEdit = new QLineEdit;
        // juste assez large pour abriter maxRemnantTime
        uint maxTextWidth = lineEdit->fontInfo().pointSize()*(maxRemnantLength + 1);
        lineEdit->setFixedWidth(maxTextWidth);
        // 0 à 99999 seuls permis
        lineEdit->setValidator(myValidator);
        // on place la durée par défaut
        setDefaultRemnantTime(lineEdit);
        // quand le texte est modifié accord en nombre avec le message qui suit
        connect(lineEdit, SIGNAL(textChanged(QString)), this, SLOT(minutes(QString)));


     label1->setBuddy(lineEdit);

     label2 = new QLabel(tr("minutes."));

     hLayout1 = new QHBoxLayout;
        hLayout1->addWidget(label1);
        hLayout1->addWidget(lineEdit);        
        hLayout1->addWidget(label2);
        hLayout1->addStretch();


 // deuxième couche horizontale de la fenêtre

    validateButton = new QPushButton(tr("&Valider"));
        connect(validateButton, SIGNAL(clicked()), this, SLOT(validate()));

    hLayout2 = new QHBoxLayout;
        hLayout2->addStretch();
        hLayout2->addWidget(validateButton);        

 // troisième couche horizontale de la fenêtre

    progressBar = new QProgressBar;
        progressBar->setHidden(true);

 // séparateur

    line = new QFrame(this);
         line->setFrameShape(QFrame::HLine);
         line->setFrameShadow(QFrame::Sunken);

 // quatrième couche horizontale de la fenêtre

    cancelButton = new QPushButton(tr("&Annuler l'arrêt prévu"));
        connect(cancelButton, SIGNAL(clicked()), this, SLOT(cancel()));
        cancelButton->setDisabled(true);
    closeButton = new QPushButton(tr("&Fermer"));
        connect(closeButton, SIGNAL(clicked()), this, SLOT(close()));

    hLayout3 = new QHBoxLayout;
        hLayout3->addWidget(cancelButton);
        hLayout3->addWidget(closeButton);        

// empilement vertical des trois couches + séparateur

    vLayout = new QVBoxLayout;
        vLayout->addLayout(hLayout1);
        vLayout->addLayout(hLayout2);
        vLayout->addWidget(progressBar);
        vLayout->addSpacing(10);
        vLayout->addWidget(line);
        vLayout->addSpacing(6);
        vLayout->addLayout(hLayout3);

    this->setLayout(vLayout);

// pas redimensionnable

    setFixedSize(sizeHint().width(), sizeHint().height());


// le chronomètre

    timer = new QTimer(this);
        timer->setInterval(timerInterval);
        connect(timer, SIGNAL(timeout()), this, SLOT(timeout()));

// le processus d'extinction de l'ordi à lancer

    myProcess = new QProcess;

// notification sonore
    noisyWarning = false;
    // utilisés resp. 90 secondes et 30 secondes avant extinction
    someNoise90 = Phonon::createPlayer(Phonon::NotificationCategory);
    someNoise30 = Phonon::createPlayer(Phonon::NotificationCategory);


#ifdef   QTSOUNDRSCBUG
    // Contournement de l'anomalie http://bugreports.qt.nokia.com/browse/QTBUG-6562"
    qtBugHack();
#endif
}

//------------------------------------------------------------------------------

// le destructeur
Zzg::~Zzg()
{
#ifdef QTSOUNDRSCBUG
    qtSoundBugCleaner();
#endif
}

//------------------------------------------------------------------------------

// appelée par clic sur le bouton Valider
void Zzg::validate()
{

    remnant = lineEdit->text().toInt() * 60 + 1; // temps restant en secondes

    progressBar->setRange(0, remnant);
    progressBar->setValue(remnant);

    timer->start(); // lance le chronomètre

    label1->setText(tr("Arrêt prévu dans :"));

    validateButton->setDisabled(true);
    cancelButton->setDefault(true);
    lineEdit->setDisabled(true);

    cancelButton->setEnabled(true);

    progressBar->setVisible(true);

}

//------------------------------------------------------------------------------

// appelée par clic sur le bouton Annuler
void Zzg::cancel()
{    
    timer->stop(); // arrêt du chrono

    validateButton->setEnabled(true);
    validateButton->setDefault(true);
    closeButton->setEnabled(true);
    lineEdit->setEnabled(true);

    cancelButton->setDisabled(true);

    // mise à disposition de la durée par défaut
    setDefaultRemnantTime(lineEdit);
    remnant = lineEdit->text().toInt() * 60 + 1;

    progressBar->setHidden(true);

    label1->setText(tr("A&rrêt souhaité de l'ordinateur dans :"));

    // arrêt de notification sonore-
    someNoise90->stop();
    someNoise30->stop();
}

//------------------------------------------------------------------------------

// appelée à chaque 'tic' du chronomètre (chaque minute)
void Zzg::timeout()
{
    remnant--;

    if (remnant == gnomeDialogDisplayTime + 30 && noisyWarning == true)
        playMyQueue(someNoise90, queue);

    if (remnant == 30 && noisyWarning == true)
        playMyQueue(someNoise30, queue);

    shutdownIf();

    // on affiche la nouvelle durée avant extinction
    if (remnant % 60 == 0)
        lineEdit->setText(QString::number(remnant / 60));

    progressBar->setValue(remnant);    
}

//------------------------------------------------------------------------------

// minutes vs minute
void Zzg::minutes(const QString time)
{
    if (time == "1" || time == "0" )
        label2->setText("minute.");
    else
        label2->setText("minutes.");
}

//------------------------------------------------------------------------------

void Zzg::gnomeShutdown()
{
    myProcess->start(gnomeCmd, gnomeArgs.split(" "));
    cancel(); // annuler dans la fenêtre de Gnome = annuler dans zzg
}

//------------------------------------------------------------------------------

void Zzg::kdeShutdown()
{    
    timer->stop();
    myProcess->start(kdeCmd, kdeArgs.split(" "));
}

//------------------------------------------------------------------------------

void Zzg::otherShutdown()
{
    qDebug() << "other";
    timer->stop();
    //myProcess->start(otherCmd, otherArgs.split(" "));
    myProcess->start(otherCmd);
}

//------------------------------------------------------------------------------

void Zzg::shutdownIf()
{    

    // la fonction d'extinction pour GNOME affiche un dialogue de GNOME
    // 60 secondes avant extinction; on la lance donc quand il reste 60 secondes
    if (remnant == gnomeDialogDisplayTime && desktop == "GNOME") {
        gnomeShutdown();
    }

    if (remnant == 0) {

        if (desktop ==  "KDE" || desktop == "KDE4")
            kdeShutdown();
        else if (desktop == "GNOME")
            gnomeShutdown();
        else
            otherShutdown();
    }    
}

//------------------------------------------------------------------------------

QString Zzg::findQdbus()
{
    QFile qdbus1("/usr/bin/qdbus");
    QFile qdbus2("/usr/lib/qt4/bin/qdbus");
    if (qdbus1.exists()) {        
        return QString("/usr/bin/qdbus");
    }
    else if (qdbus2.exists()) {                
        return QString("/usr/lib/qt4/bin/qdbus");
    }
    else
        return QString();
}

//------------------------------------------------------------------------------

void Zzg::setNoisyWarning(bool option)
{
    noisyWarning = option;
}

//------------------------------------------------------------------------------

void Zzg::qtBugHack()
{    
        qDebug();
        qDebug() << QObject::tr("\n*** Contournement de l'anomalie http://bugreports.qt.nokia.com/browse/QTBUG-6562");
        qDebug() << QObject::tr("par recopie des fichiers de son sur le disque");

        QDir rsc(soundResourceDir);
        qDebug() << soundResourceDir;
        soundFiles = QStringList(rsc.entryList());
        foreach (QString name, soundFiles)
        {
            QString fileFromName (soundResourceDir + name);
            QString fileToName (soundCopyDir + name);

            qDebug() << QObject::tr("Copie de %1 vers %2").arg(fileFromName).arg(fileToName);
            qDebug();
            QFile::copy(fileFromName, fileToName);
        }        
}

//------------------------------------------------------------------------------

void Zzg::qtSoundBugCleaner()
{
    // nettoyage des fichiers copiés
    qDebug();
    qDebug() << QObject::tr("*** Suite du contournement de http://bugreports.qt.nokia.com/browse/QTBUG-6562");

    foreach (QString name, soundFiles)
    {
            QString fileToRemove(soundCopyDir + name);

            qDebug() << QObject::tr("Suppression de %1").arg(fileToRemove);
            qDebug();

            QFile::remove(soundCopyDir + name);
    }

}

//------------------------------------------------------------------------------

QList<QUrl> Zzg::setNotificationSoundQueue()
{
    QList<QUrl> queue;
    for (int i = 0; i < 12; i++)
        queue << QUrl::fromLocalFile(soundToPlayDir + "Explosion.wav");
    return queue;
}

//------------------------------------------------------------------------------

void Zzg::setDefaultRemnantTime(QLineEdit *lineEd)
{
    lineEd->setText(QString::number(defaultRemnantTime));
    lineEd->selectAll();
    lineEd->setFocus();
}

//------------------------------------------------------------------------------

void Zzg::playMyQueue(Phonon::MediaObject *mediaObject, const QList<QUrl> list)
{
    mediaObject->clear();
    mediaObject->setQueue(list);
    mediaObject->play();
}

//------------------------------------------------------------------------------

// FIN ZZG.CPP
